13.1 评论在数据库中的表示
从psots表到comments表是一对多关系,
从users表到comments表也是一对多关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| # ... class Comment(db.Model): __tablename__ = 'comments' id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) body_html = db.Column(db.Text) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) disabled = db.Column(db.Boolean) # 协管员通过该字段查禁不当评论 author_id = db.Column(db.Integer, db.ForeignKey('users.id')) post_is = db.Column(db.Integer, db.ForeignKey('posts.id')) @staticmethod def on_changed_body(target, value, oldvalue, initiator): allowed_tags = ['a', 'abbr', 'acronym', 'b', 'code', 'em', 'i', 'strong'] # 将Markdown源文本转换为合乎需要的HTNL文本 target.body_html = bleach.linkify(bleach.clean( markdown(value, output_format='html'), tags=allowed_tags, strip=True)) db.event.listen(Comment.body, 'set', Comment.on_changed_body)
|
2. 在app/models.py
中定义users表和posts表与comennts表的一对多关系:
1 2 3 4 5 6 7 8 9 10
| # ... class User(UserMixin, db.Model): # ... comments = db.relationship('Comment', backref='author', lazy='dynamic') class Post(db.Model): # ... comments = db.relationship('Comment', backref='post', layz='dynamic')
|
13.2 显示评论框和提交评论
1. 在app/main/forms.py
中定义评论表单:
1 2 3 4 5
| # ... class CommentForm(FlaskForm): body = StringField('Enter your comment', validators=[DataRequired()]) submit = SubmitField('Submit')
|
2. 在app/main/views.py
中定义在文章页显示评论的路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @main.route('/post/<int:id>', methods=['GET', 'POST']) def post(id): post = Post.query.get_or_404(id) form = CommentForm() if form.validate_on_submit(): comment = Comment(body=form.body.data, post=post, author=current_user._get_current_object()) db.session.add(comment) db.session.commit() flash('Your comment has been published.') return redirect(url_for('main.post', id=post.id, page=-1)) page = request.args.get('page', 1, type=int) if page == -1: # 用总评论数除以每页显示数量再加1获得真正需要请求的页码,使得提交评论后能转到看到自己评论的页面 page = (post.comments.count() - 1) // current_app.config['FLASKY_COMMENTS_PER_PAGE'] + 1 pagination = post.comments.order_by(Comment.timestamp.asc()).paginate( page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], error_out=False) comments = pagination.items return render_template('post.html', posts=[post], form=form, comments=comments, pagination=pagination)
|
注意:
- 和
Post
模型一样,评论的author
字段不能直接设为current_user
,因为这个变量是上下文代理对象,要使用current_user._get_current_object()
获取真正的用户对象。
url_for()
函数的page
参数设为-1
,用来请求最后一页,以达到提交评论后转到可以看到自己刚才发表的评论的页面。真正的页码通过(用总评论数除以每页显示数量)再加1获得。
3. 在app/templates/_post.html
中添加到博客文章评论的链接:
1 2 3 4 5 6 7
| # ... <a href="{{ url_for('main.post', id=post.id)}}#comments"> <span class="label label-primary"> {{ post.comments.count() }} Comments </span> </a>
|
注意:url_for()
函数后面加了一个#comments
后缀,这个后缀称为URL片段,其作用是用于指定加载页面后滚动条所在的初始位置。该例中,Web浏览器会寻找id为comments
的元素并滚动页面。
13.3 管理评论
1. 在app/templates/base.html
中在顶端导航条添加管理评论的链接:
1 2 3 4 5
| # ... {% if current_user.can(Permission.MODERATE_COMMENT) %} <li><a href="{{ url_for('main.moderate') }}">Moderate</a></li> {% endif %} # ...
|
2. 在app/main/views.py
中定义管理评论的路由(用于显示所有评论):
1 2 3 4 5 6 7 8 9 10 11 12 13
| # ... @main.route('/moderate') @login_required @permission_required(Permission.MODERATE_COMMENT) def moderate(): page = request.args.get('page', 1, type=int) pagination =Comment.query.order_by(Comment.timestamp.desc()).paginate( page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], error_out=False) comments = pagination.items return render_template('moderate.html', comments=comments, pagination=pagination, page=page)
|
3. 在app/templates/moderate.html
中定义管理评论的页面模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| {% extends "base.html" %} {% import "_macros.html" as macros %} {% block title %}Flasky - Comment Moderation{% endblock %} {% block page_content %} <div class="page-header"> <h1>Comment Moderation</h1> </div> {% set moderate = True %} {% include '_comments.html' %} {% if pagination %} <div class="pagination"> {{ macros.pagination_widget(pagination, '.moderate') }} </div> {% endif %} {% endblock %}
|
- 模板中通过Jinja2提供的
set
指令定义了一个模板变量moderate
,并将其值设为True
,这个变量的作用是决定在_comments.html
模板中是否渲染管理评论功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <ul class="comments"> {% for comment in comments %} <li class="comment"> <div class="comment-thumbnail"> <a href="{{ url_for('main.user', username=comment.author.username) }}"> <img class="img-rounded profile-thumbnail" src="{{ comment.author.gravatar(size=40) }}"> </a> </div> <div class="comment-content"> <div class="comment-date">{{ moment(comment.timestamp).fromNow() }}</div> <div class="comment-author"><a href="{{ url_for('main.user', username=comment.author.username) }}">{{ comment.author.username }}</a></div> <div class="comment-body"> {% if comment.disabled %} # 不当的评论用以下内容显示 <p><i>This comment has been disabled by a moderator.</i></p> {% endif %} {% if moderate or not comment.disabled %} {% if comment.body_html %} {{ comment.body_html | safe }} {% else %} {{ comment.body }} {% endif %} {% endif %} </div> {% if moderate %} <br> {% if comment.disabled %} <a class="btn btn-default btn-xs" href="{{ url_for('main.moderate_enable', id=comment.id, page=page) }}">Enable</a> {% else %} <a class="btn btn-danger btn-xs" href="{{ url_for('main.moderate_disable', id=comment.id, page=page) }}">Disable</a> {% endif %} {% endif %} </div> </li> {% endfor %} </ul>
|
5. 在app/main/views.py
中定义显示评论、禁用评论的路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| # 显示评论 @main.route('/moderate/enable/<int:id>') # 评论的id @login_required @permission_required(Permission.MODERATE_COMMENT) def moderate_enable(id): comment = Comment.query.get_or_404(id) comment.disabled = False db.session.add(comment) return redirect(url_for('main.moderate', page=request.args.get('page', 1, type=int))) # 禁用评论 @main.route('/moderate/disable/<int:id>') # 评论的id @login_required @permission_required(Permission.MODERATE_COMMENT) def moderate_disable(id): comment = Comment.query.get_or_404(id) comment.disabled = True db.session.add(comment) return redirect(url_for('main.moderate', page=request.args.get('page', 1, type=int)))
|
redirect()
函数中设置了page
参数,如果之前的请求中指定了page
参数,则会将其转入重定向中。